/*
 *  polynomial.h
 *  radici
 *
 *  Created by Nicola Gigante on 04/03/08.
 *  Copyright 2008 Nicola Gigante.
 *
 */

#ifndef __POLYNOMIAL_H__
#define __POLYNOMIAL_H__

#define _USE_MATH_DEFINES

#include <cmath>
#include <climits>
#include <ctime>
#include <cstdlib>
#include <iostream>
#include <algorithm>

#include <QList>
#include <QtDebug>

#include "complex.h"

using namespace std;

/*! 
	\brief Il typedef Real è usato dappertutto nel codice al posto di double.
 E' altamente consigliato utilizzare Real invece di double per facilitare
 una probabile futura migrazione a long double.
 \warning Cambiare questo typedef non è sufficiente perchè molto codice utilizza
		  funzioni della libreria matematica che ricevono semplici double.
 */
typedef double Real;

/*!
	\brief Classe che rappresenta e manipola un polinomio di grado N in variabile complessa.
 
 La classe Polynomial è fondamental per tutti gli algoritmi alla base dei grafici
 prodotti da Perceel. Essa infatti rappresenta un polinomio di variabile complessa.
 
 Tutte le operazioni svolte sui coefficienti del polinomio che possono, ovviamente, essere anche numeri reali,
 sono svolte secondo le regole dell'aritmetica complessa. La classe utilizzata per la rappresentazione
 dei numeri complessi è la classe std::complex della libreria standard del C++.
 La classe Polynomial fornisce metodi per eseguire le seguenti operazioni algebriche:
	- Addizione tramite il metodo add(Polynomial)
	- Moltiplicazione tramite i vari metodi multiply()
 Inoltre è possibile:
	- Normalizzare il polinomio con la funzione norm(), ovvero dividere tutti i coefficienti
	  per il coefficiente del termine di grado maggiore.
	- Valutare il valore del polynomio in corrispondenza di un dato valore della variabile, tramite la funzione eval(Complex)
	- Ricercare le radici del polinomio tramite il metodo di Durand-Kerner, vedi la funzione roots()
	- Invertire i coefficienti del polinomio, tramite la funzione reverse()
 La funzione print() stampa il polynomio sullo standard output. E' possibile scegliere quale lettera
 usare come variabile utilizzando setVariable(char). La variabile di default è 's'.
 Per costruire un polinomio, i termini possono essere aggiunti in due modi:
	- La funzione setTerm(int, Complex) imposta il coefficiente del termine con dato esponente
	- La funzione addTerm(int, Complex) aggiunge il coefficiente al corrispondente valore già presente nel polinomio.
 Il polinomio può essere costruito anche tramite la funzione statica complexToPolynomial, che non usa il numero complesso
 come coefficiente di grado zero, come il costruttore analogo, ma crea invece un binomio con la parte reale e immaginaria
 del numero complesso.
 Le seguenti informazioni sono disponibil per un oggetto Polynomial:
	- Il grado del polinomio, tramite la funzione grade()
	- Il valore massimo dei coefficienti del polinomio, dalla funzione maxTermModule()
	- Se il polynomio è stato creato con la funzione complexToPolynomial(Complex), la funzione
	  isFromComplex() restituisce true.
 */
class Polynomial {
	
	QList<Complex > m_terms; //!< Lista dei coefficienti del polinomio. L'indice della lista corrisponde all'esponente.
	
	bool m_complex;	//!< Variabile di stato per la funzione isFromComplex()
	char m_variable; //!< Carattere utilizzato per la variabile durante la stampa.
	
public:
	
	/* Costruttori */
	Polynomial() : m_complex(false), m_variable('s') {} //!< Costruttore di default. Polinomio vuoto P(x) = 0
	Polynomial(const Polynomial &p); //!< Costruttore di copie, crea un polinomio identico al parametro
	Polynomial(Complex known_term); //!< Questo costrutture crea un polinomio e ne imposta il termine noto
	Polynomial(Real known_term); //!< Questo costruttore crea un polinomio e ne imposta il termine noto
	
	static Polynomial complexToPolynomial(Complex c); //!< Crea un polinomio separando la parte reale e immaginaria del numero complesso c
	
	/* Accesso ai termini */
	void addTerm(int exp, Complex k); //!< Aggiunge al coefficiente del termine di grado exp, il numero complesso k
	void setTerm(int exp, Complex k); //!< Imposta a k il coefficiente del termine di grado exp.
	
	char variable() { return m_variable; } //!< Ritorna il carattere impostato per la variabile durante la stampa
	void setVariable(char variable) { m_variable = variable; } //!< Imposta il carattere per la variabile durante la stampa
	
	Complex term(int exp) const; //!< Restituisce il coefficiente del termine di grado exp
	
	QList<Complex > terms() const { return m_terms; } //!< Restituisce la lista dei coefficienti ordinati per esponente.
	
	int grade() const { return m_terms.count() - 1; } //!< Restituisce il grado del polinomio
	long double maxTermModule(); //!< Restituisce il valore massimo dei coefficienti del polinomio
	bool isFromComplex() { return m_complex; } //!< Restituisce true se l'oggetto è stato creato con complexToPolynomial(Complex)
	
	/* Manipolazione sul polinomio */
	Polynomial norm(); //!< Normalizza il polinomio
	Complex  eval(Complex val); //!< Valuta il valore del polinomio in corrispondenza del valore val
	QList<Complex > roots(); //!< Ricerca con il metodo di Durand-Kerner le radici complesse del polinomio
	Polynomial reverse(); //!< Restituisce un polinomio con l'ordine dei coefficienti invertito.
	
	/* Operazioni algebriche */
	Polynomial add(Polynomial p); //!< Restituisce il polinomio somma.
	Polynomial multiply(Polynomial p); //!< Restituisce il prodotto.
	Polynomial multiply(Complex c); //!< Restituisce il prodotto.
	Polynomial multiply(Real r); //!< Restituisce il prodotto.
	
	/* Operatori */
	Polynomial operator +(Polynomial p) {return this->add(p); } //!< Operatore sovraccaricato per la somma.
	
	Polynomial operator *(Polynomial p) { return this->multiply(p); } //!< Operatore sovraccaricato per la moltiplicazione.
	Polynomial operator *(Complex c) { return this->multiply(c); } //!< Operatore sovraccaricato per la moltiplicazione.
	Polynomial operator *(Real r) { return this->multiply(r); } //!< Operatore sovraccaricato per la moltiplicazione.
	
	/*! 
	   \brief Operatore che valuta il polinomio in corrispondenza del valore val.
	 
	 Grazie a questo operatore è possibile utilizzare per la valutazione del polinomio
	 la sintassi tipica usata in matematica. Per esempio un polinomio p può essere valutato
	 nel valore 5 con la sintassi p(5).
	 */
	Complex operator ()(Complex val) { return this->eval(val); } 
	
	/* I/O */
	void print(); //!< Stampa il polinomio sullo standard output
};

#endif
